package repos

import javax.inject.Inject

import exceptions.LoginNotFoundUserError
import models._
import org.joda.time.DateTime
import play.api.db.slick.DatabaseConfigProvider
import play.api.libs.Codecs
import repos.Tables.UserTable
import slick.backend.DatabaseConfig
import slick.jdbc.JdbcProfile

import scala.collection.JavaConverters._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.reflect.ClassTag
import scala.util.Try

class UserRepository @Inject()(databaseConfigProvider: DatabaseConfigProvider,
                               cache: Pool,
                               roleRepository: RoleRepository,
                               userRoleRepository: UserRoleRepository,
                               roleModuleRepository: RoleModuleRepository
                              )
  extends BaseRepository[User, UserTable](Tables.users, cache) {
  override val dbConfig: DatabaseConfig[JdbcProfile] = databaseConfigProvider.get[JdbcProfile]

  import dbConfig.driver.api._

  override def getById(id: Int)(implicit abc: ClassTag[User]): Future[Option[User]] = {
    super.getById(id).map(ou => ou.map(u => u.copy(password = "******")))
  }

  def getByUserNameAndPassword(userName: String, password: String) = {
    db.run(qq.filter(u => u.userName === userName && u.password === password).result.headOption.map(ou => ou.map(u => u.copy(password = "******"))))
  }

  def login(userName: String, password: String): Future[Profile] = {
    for {
      ou <- getByUserNameAndPassword(userName, password)
      prf <- ou.map { user =>
        //        db.run(
        //          sql"""SELECT r.* FROM USER u JOIN userRole ur ON u.userId=u.id
        //               JOIN role r on r.id = ur.roleId
        //               WHERE u.userId = {user.id.get}
        //             """.as[Role])
        db.run(userRoleRepository.qq.join(roleRepository.qq).on((ur, r) => ur.roleId === r.id).filter { case (ur, r) => ur.userId === user.id.get }.map { case (ur, r) => r }.result)
          .map { roles =>
            val profile = Profile(user, roles)
            val profileUserMap = Map[String, String](
              "userName" -> user.userName,
              "password" -> user.password,
              "email" -> user.email,
              "userStatus" -> user.userStatus.toString,
              "joinTime" -> (user.joinTime match {
                case Some(j) => j.getMillis.toString
                case None=> ""
              }),
              "mobilePhoneNo" -> user.mobilePhoneNo.getOrElse(""),
              "nickName" -> user.nickName.getOrElse(""),
              "id" -> user.id.get.toString,
              "updateTime" -> user.updateTime.map(_.getMillis.toString).getOrElse(""),
              "isDeleted" -> user.isDeleted.toString
            ).asJava
            roles.foreach { role =>
              val roleKey = s"Profile.${user.id.get}.role"
              val roleIdKey = roleKey + s".${role.id.get}"

              cache.withJedisClient { client =>
                client.sadd(roleKey, roleIdKey)
                client.hset(roleIdKey, "roleCode", role.roleCode)
                client.hset(roleIdKey, "roleName", role.roleName)
                client.hset(roleIdKey, "joinTime", (role.joinTime match {
                  case Some(j) => j.getMillis.toString
                  case None => ""
                }))
                client.hset(roleIdKey, "isDeleted", role.isDeleted.toString)
                client.hset(roleIdKey, "roleDescrition", role.roleDescrition.getOrElse(""))
                client.hset(roleIdKey, "updateTime", role.updateTime.map(_.getMillis.toString).getOrElse(""))
                client.hset(roleIdKey, "id", role.id.get.toString)
              }
            }
            cache.withJedisClient(client => client.hmset(s"Profile.${user.id.get}.user", profileUserMap))
            cache.withJedisClient { client =>
              client.hset(s"Profile.${user.id.get}", "user", s"Profile.${user.id.get}.user")
              client.hset(s"Profile.${user.id.get}", "role", s"Profile.${user.id.get}.role")
            }
            profile
          }
      } match {
        case Some(prf) =>
          prf
        case None =>
          throw new LoginNotFoundUserError
      }
    } yield prf
  }

  def loginToken(userName: String, password: String): Future[SToken] = {
    val future1 = getByUserNameAndPassword(userName, password).flatMap { case Some(user) =>
      db.run((userRoleRepository.qq.join(roleRepository.qq).on((ur, r) => ur.roleId === r.id)
        .joinLeft(roleModuleRepository.qq).on {
        case ((ur, r), rm) =>
          r.id === rm.roleId
      }
        .filter { case ((ur, r), rm) =>
          ur.userId === user.id.get
        }
        .map { case ((ur, r), rm) =>
          (ur.userId, r.id, rm.map(_.moduleId))
        }
        .result
        )
      )
    }
      .map { userRoles =>
        val tokens = userRoles.groupBy(_._1).map { case (key, values) =>
          val roles = values.groupBy(_._2).map { case (key, values) =>
            SProfileRoles(key, values.map(_._3).filter(_.nonEmpty).map(_.get), Seq())
          }.toSeq
          val profile = SProfile(key, roles)
          val token = SToken(Codecs.sha1(profile.userId.toString), profile, None)
          token
        }.toSeq
        tokens(0)
    }
    future1
  }

  def getProfile(userKey: String):Option[Profile] = {
    cache.withJedisClient(client => {
      val profileKey = s"Profile.${userKey}"
      val profileKeyMap= client.hgetAll(profileKey).asScala.toMap match {
        case x:Map[String,String] if !x.isEmpty => x
        case _ => null
      }
      val result = Option(profileKeyMap).flatMap { case profileKeys if !profileKeys.isEmpty =>
        val pUserKey = profileKeys.get("user")
        val pRoleIdKey = profileKeys.get("role")

        val oUser = pUserKey.map { pUserKey =>

          val userMap = client.hgetAll(pUserKey)

          val user = User(
            userMap.get("userName"),
            userMap.get("password"),
            userMap.get("email"),
            userMap.get("userStatus"),
            Some(new DateTime(userMap.get("joinTime").toLong)),
            Option(userMap.get("mobilePhoneNo")),
            Option(userMap.get("nickName")),
            Option(userMap.get("id").toInt),
            Try{new DateTime(userMap.get("updateTime").toLong)}.toOption,
            userMap.get("isDeleted").toBoolean
          )
          user
        }

        val oRoles = pRoleIdKey.map { pRoleIdKey =>
          val roles = client.smembers(pRoleIdKey).asScala.toSeq.map { roleKey =>
            val roleSet = client.hgetAll(roleKey)
//            println("---" * 13 + roleSet)
            Role(
              roleSet.get("roleCode"),
              roleSet.get("roleName"),
              Some(new DateTime(roleSet.get("joinTime").toLong)),
              roleSet.get("isDeleted").toBoolean,
              Option(roleSet.get("roleDescrition")),
              Try{new DateTime(roleSet.get("updateTime").toLong)}.toOption,
              Option(roleSet.get("id").toInt)
            )
          }
          roles
        }
        oUser flatMap { user =>
          oRoles map { roles =>
            Profile(user, roles)
          }
        }
      }
      result
    })
  }

}
